#pragma once
#include <coroutine>
#include <memory>
#include <string>
using namespace std;

namespace nco
{
	template <class ReturnType, class YieldType>
	class Task;

	template <class ReturnType, class PromiseType>
	class TaskReturn
	{
	public:
		TaskReturn(PromiseType *p) : m_promise(p) {}
		bool hasReturn()
		{
			return m_promise->hasReturn();
		}
		ReturnType getReturn()
		{
			return m_promise->getReturn();
		}

	private:
		PromiseType *m_promise;
	};
	template <class PromiseType>
	class TaskReturn<void,PromiseType>
	{
	public:
		TaskReturn(PromiseType *p) : m_promise(p) {}
		bool hasReturn()
		{return m_promise->hasReturn();}
	private:
		PromiseType *m_promise;
	};

	template <class YieldType,class PromiseType>
	class TaskYield
	{
	public:
		TaskYield(PromiseType *p):m_promise(p){}
		bool hasYield()
		{return m_promise->hasYieldValue();}
		YieldType getYield()
		{return m_promise->getYieldValue();}
	private:
		PromiseType* m_promise;
	};

	template <class PromiseType>
	class TaskYield<void,PromiseType>
	{
	public:
		TaskYield(PromiseType* p){}
	};
	template <class ReturnType>
	class PromiseReturn
	{
	public:
		void return_value(ReturnType v)
		{
			m_rv = v;
		};
		bool hasReturn()
		{
			return m_rv.has_value();
		}
		ReturnType &getReturn()
		{
			return m_rv.value();
		}

	private:
		optional<ReturnType> m_rv;
	};
	template <>
	class PromiseReturn<void>
	{
	public:
		void return_void() { m_isRet = true; }
		bool hasReturn() { return m_isRet; }

	private:
		bool m_isRet = false;
	};


	template <class YieldType>
	class PromiseYield
	{
	public:
		auto yield_value(YieldType v)
		{
			m_yv = v;
			return suspend_always{};
		}
		YieldType getYieldValue()
		{
			return m_yv.value();
		}
		bool hasYieldValue()
		{
			return m_yv.has_value();
		}

	private:
		optional<YieldType> m_yv;
	};

	template <>
	class PromiseYield<void>
	{
		
	};


	template <class ReturnType, class YieldType>
	class BasePromise : public PromiseReturn<ReturnType>,public PromiseYield<YieldType>
	{
	public:
		using TaskType = Task<ReturnType, YieldType>;
		TaskType get_return_object()
		{
			return {this};
		}
		auto initial_suspend()
		{
			return suspend_never{};
		}
		auto final_suspend() noexcept
		{
			return suspend_never{};
		}
		void unhandled_exception()
		{
		}
	};

	template <class ReturnType = void, class YieldType = void>
	class Task : public TaskReturn<ReturnType, BasePromise<ReturnType, YieldType>>, 
				public	TaskYield<YieldType, BasePromise<ReturnType, YieldType>>
	{
	public:

		using promise_type = BasePromise<ReturnType, YieldType>;
		using handle = std::coroutine_handle<promise_type>;
		

		Task(promise_type *p)
			: TaskReturn<ReturnType, BasePromise<ReturnType, YieldType>>(p),
				TaskYield<YieldType, BasePromise<ReturnType, YieldType>>(p),
			m_promise(p), m_handle(handle::from_promise(*p)) {}


		bool done()
		{
			return m_handle.done();
		}
		operator bool()
		{
			return (bool) m_handle;
		}
		void remuse()
		{
			m_handle.resume();
		}
	private:
		promise_type *m_promise;
		handle m_handle;
	};

}